#include "VoiceRecognizer.h"

void (VoiceRecognizer::*confirmedFunction)(void);

//(Emergency?) Exit routine
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType){
	std::wcout << "Exiting..."<<std::endl;
	//done=true;
	return TRUE;
}


VoiceRecognizer::VoiceRecognizer(CommandHandler *commandHandler){
	//Set local references
	this->commandHandler=commandHandler;
	this->speaker=commandHandler->getSpeaker();

	done=true; //Set to false upon listen(true) call
	confirmPhrase=false;
	SetConsoleCtrlHandler(HandlerRoutine, TRUE);//Define exit routine
	
	hr = CoInitialize(0);//Initializes the COM library on the current thread
	if(FAILED(hr)){
		std::wcout<<"Could not initialize the VoiceRecognizer."<<std::endl;
		return;
	}

	//Create COM context.
	//Build the engine as 'shared recognizer': other applications may use it, too
	//hr = cpEngine.CoCreateInstance(CLSID_SpSharedRecognizer); //Use same recognizer for windows and application commands
	hr = cpEngine.CoCreateInstance( CLSID_SpInprocRecognizer ); // Use recognizer for application commands only
	if (FAILED(hr)) {
		std::wcout<<"Could not create engine instance."<<std::endl;
		return;
	}

	//Set the context, i.e. the environment in which the VoiceRecognizer will listen
	hr = cpEngine->CreateRecoContext(&cpRecoCtx);
	if (FAILED(hr)) {
		std::wcout<<"Could not create recognition context."<<std::endl;
		return;
	}

	//Define, which window will receive the notifications
	hr=cpRecoCtx->SetNotifyWin32Event();
	if (FAILED(hr)) {
		std::wcout<<"Could not set context handler."<<std::endl;
		return;
	}
	cpRecoCtx->Pause(NULL);//Do not listen during initialization


	//**************************
	//START INPUTCHANNEL SECTION
	//**************************
	//Section to handle input channel. Without this section, the whole operating system would listen to the input. Meaning, e.g. by
	//saying "Start", the OS will receive this command, too, and open the start menu. Therefore, we must NOT enable the "overall" listener,
	//but we must create a "special" input channel, just for the application.
	CComPtr<ISpObjectToken> pAudioToken;
	CComPtr<ISpAudio> pAudio;
	hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &pAudioToken);// Use the default input
	if (FAILED(hr)) {
		std::wcout<<"Could not set default token for Audio in."<<std::endl;
		return;
	}
	hr = cpEngine->SetInput(pAudioToken, TRUE);
	if (FAILED(hr)) {
		std::wcout<<"Could not set input channel for audio token (could not connect to device)."<<std::endl;
		return;
	}
	hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &pAudio);
	if (FAILED(hr)) {
		std::wcout<<"Could not create default input channel."<<std::endl;
		return;
	}
	hr = cpEngine->SetInput(pAudio, TRUE);
	if (FAILED(hr)) {
		std::wcout<<"Could not set input channel for audio channel."<<std::endl;
		return;
	}
	//**************************
	//END INPUTCHANNEL SECTION
	//**************************


	//Receive handle of the event that will be signaled after exception
	hEvent = cpRecoCtx->GetNotifyEventHandle();

	//Define, that we listen to both the phrases understood and the ones not understood
	ullEvents = SPFEI(SPEI_RECOGNITION)| SPFEI(SPEI_FALSE_RECOGNITION);
	//Tell SAPI, what the recognizer is interested in, i.e. which events it should be listening to
	hr=cpRecoCtx->SetInterest(ullEvents, ullEvents);
	if (FAILED(hr)) {
		std::wcout<<"Could not set interests."<<std::endl;
		return;
	}

	//Create an instance of the grammar
	hr = cpRecoCtx->CreateGrammar(1, &cpGram);
	if (FAILED(hr)) {
		std::wcout<<"Could not set grammar."<<std::endl;
		return;
	}

	//Modular design: use other class to define and create the grammar
	Grammar *gr = new Grammar(cpGram);
	gr->init();
	//Now, the local cpGram has been updated

	//Commit the modifications (that were actually done by the external class)
	hr = cpGram->Commit(NULL);
	if (FAILED(hr)) {
		std::wcout<<"Could not commit grammar changes."<<std::endl;
		return;
	}

	//Let listener listen again after modifications
	hr = cpGram->SetGrammarState(SPGS_ENABLED);
	if (FAILED(hr)) {
		std::wcout<<"Could not enable grammar state."<<std::endl;
		return;
	}


	//Tell SAPI to rely on rules
	hr= cpGram->SetRuleState(0,0,SPRS_ACTIVE);
	if (FAILED(hr)) {
		std::wcout<<"Could not set rule states as 'active'."<<std::endl;
		return;
	}
	
}

VoiceRecognizer::~VoiceRecognizer(void){
	//Release grammar, context, engine and uninitialize afterwards
	cpGram.Release();
	cpRecoCtx.Release();
	cpEngine.Release();
	CoUninitialize();
}



//Listen to voice input?
void VoiceRecognizer::listen(bool listen){
	if(listen && done){
		//Start listening
		done=false;
		run();
	}
	else if(!listen && !done){
		//Stop listening
		done=false;
	}
	//else: no modifications

}

//The listening loop
void VoiceRecognizer::run(){
	speaker->Say(L"I am listening...");
	cpRecoCtx->Resume(NULL);
	//Unless stop, i.e. 'listen(false)' was called
	while(!done){
		//While could successfully listen to context
		while(evt.GetFrom(cpRecoCtx)==S_OK){
			//Context recognition event
			cpRecoCtx->Pause(NULL); //Stop listening while analyzing
			if(evt.eEventId==SPEI_FALSE_RECOGNITION){
				//Not understood phrase
				notRecognized();
			}
			else{
				//Understood phrase: store recognition result in variable
				pPhrase = evt.RecoResult();
				recognized(pPhrase);
			}
			cpRecoCtx->Resume(NULL); //Restart listening after analysis

			//Clear commands that were in "recognized queue"
			while(evt.GetFrom(cpRecoCtx)){
				if(evt.eEventId!=SPEI_FALSE_RECOGNITION)
				;//evt.RecoResult();
			}
		}
	}
}

void VoiceRecognizer::notRecognized(void){
	speaker->Say(L"I beg you pardon?");
}

void VoiceRecognizer::recognized(ISpPhrase *pPhrase){
	//Store the parts of the phrase in the pParts variable
	hr=pPhrase->GetPhrase(&pParts);
	if (FAILED(hr)) {
		std::wcout<<"Could not get parts from the phrase that was understood."<<std::endl;
		return;
	}

	//Store the rule
	SPPHRASERULE rule =pParts->Rule;

	//Read the actual text that was understood
	hr=pPhrase->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, FALSE, &text, 0);
	if (FAILED(hr)) {
		std::wcout<<"Could not read text from the phrase that was understood."<<std::endl;
		return;
	}
	
	//Confirm the phrase that was understood
	speaker->Say(text);
	std::wcout << text << " ("<<pParts->Rule.Confidence<<") " <<std::endl;

	//If we are at a confirm phase, then we only accept a confirmation or cancellation. Everything else is ignored and understood as cancellation.
	if(confirmPhrase){
		if(rule.ulId==RULE_CONFIRM){
			speaker->Say(L"Command confirmed.");
			//Execute the to-be-confirmed function
			if(confirmedFunction!=NULL)
				(this->*confirmedFunction)();
		}
		else
			speaker->Say(L"Command cancelled.");
		//Reset confirm phrase status and function
		/*confirmPhrase=false;*/
		confPhrase(false);
		confirmedFunction=NULL;
		return;
	}

	switch(rule.ulId){
	case RULE_START_GAME:
		commandHandler->setGameState(false, START);
		break;
	case RULE_STOP_GAME:
		/*confirmPhrase=true;*/
		confPhrase(true);
		confirmedFunction=&VoiceRecognizer::doStop;
		speaker->Say(L"Please confirm: do you want to stop the game?");

		break;
	case RULE_PAUSE_GAME:
		commandHandler->setGameState(false, PAUSE);
		break;
	case RULE_RESUME_GAME:
		commandHandler->setGameState(false, RESUME);
		break;
	case RULE_RESET_GAME:
		/*confirmPhrase=true;*/
		confPhrase(true);
		speaker->Say(L"Please confirm: do you want to reset the game?");
		confirmedFunction=&VoiceRecognizer::doReset;
		break;
	case RULE_RUNTIME_GRAB:
		commandHandler->setAction(false, PICK);
		break;
	case RULE_RUNTIME_GO:
		commandHandler->setAction(false, RELEASE);
		break;
	}


	//Release memory
	CoTaskMemFree(pParts);
	CoTaskMemFree(text);

}

void VoiceRecognizer::confPhrase(bool conf){
	int general[7] = {RULE_START_GAME, RULE_PAUSE_GAME, RULE_RESUME_GAME, RULE_RESET_GAME, RULE_STOP_GAME, RULE_RUNTIME_GRAB, RULE_RUNTIME_GO};
	int confirm[2]={RULE_CONFIRM, RULE_CANCEL};
	cpGram->SetGrammarState(SPGS_DISABLED);
	confirmPhrase=conf;
		for(int i=0; i<7; i++)
			cpGram->SetRuleIdState(general[i], conf? SPRS_INACTIVE : SPRS_ACTIVE);
		for(int i=0; i<2; i++)
			cpGram->SetRuleIdState(confirm[i], conf? SPRS_ACTIVE : SPRS_INACTIVE);
		cpGram->Commit(NULL);
		cpGram->SetGrammarState(SPGS_ENABLED);
	
}

void VoiceRecognizer::doStop(){
	commandHandler->setGameState(false, STOP);
}
void VoiceRecognizer::doReset(){
	commandHandler->setGameState(false, RESET);
}